package com.flow.server.modules.model.service.impl;

import com.fasterxml.jackson.databind.node.ObjectNode;
import com.flow.server.common.exception.SysWorkflowException;
import com.flow.server.modules.base.service.impl.IBaseServiceImpl;
import com.flow.server.modules.cmd.BpmnBuilderUtil;
import com.flow.server.modules.cmd.BpmnModelCmd;
import com.flow.server.modules.definition.mapper.DefinitionMapper;
import com.flow.server.modules.model.mapper.ModelRestMapper;
import com.flow.server.modules.model.service.IModelRestService;
import com.google.common.collect.Maps;
import org.activiti.bpmn.converter.BpmnXMLConverter;
import org.activiti.bpmn.model.BpmnModel;
import org.activiti.engine.impl.persistence.entity.ModelEntity;
import org.activiti.engine.impl.persistence.entity.ModelEntityImpl;
import org.activiti.engine.repository.Deployment;
import org.activiti.engine.repository.Model;
import org.activiti.engine.repository.ModelQuery;
import org.activiti.engine.repository.ProcessDefinition;
import org.apache.batik.transcoder.TranscoderException;
import org.apache.batik.transcoder.TranscoderInput;
import org.apache.batik.transcoder.TranscoderOutput;
import org.apache.batik.transcoder.image.PNGTranscoder;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.xml.stream.XMLInputFactory;
import javax.xml.stream.XMLStreamReader;
import java.io.*;
import java.nio.charset.StandardCharsets;
import java.util.List;
import java.util.Map;
import java.util.Objects;

/**
 * 描述：流程模型 Service 实现类
 * <p>
 * 作者：hutongfu
 * 时间：2020/1/18 11:20 星期六
 */
@Service
public class IModelServiceImpl extends IBaseServiceImpl implements IModelRestService {

    @Resource
    private ModelRestMapper modelRestMapper;

    @Resource
    private DefinitionMapper definitionMapper;

    @Transactional(propagation = Propagation.REQUIRED, rollbackFor = Exception.class)
    @Override
    public void deleteById(String modelId) {
        try {
            //1.删除已部署的模型
            List<Model> deployedList = repositoryService.createModelQuery().modelId(modelId).deployed().list();
            deployedList.forEach(model -> {
                ModelEntity modelEntity = (ModelEntityImpl) model;
                modelRestMapper.deleteById(modelId);
                modelRestMapper.deleteByteArrayByDeployId(modelEntity.getDeploymentId());
                modelRestMapper.deleteByteArrayById(modelEntity.getEditorSourceValueId());
                modelRestMapper.deleteByteArrayById(modelEntity.getEditorSourceExtraValueId());
                modelRestMapper.deleteDeployedById(modelEntity.getDeploymentId());
                List<ProcessDefinition> definitionList = repositoryService.createProcessDefinitionQuery()
                        .deploymentId(model.getDeploymentId()).list();
                definitionList.parallelStream().forEach(definition -> {
                    definitionMapper.deleteByDeployId(modelEntity.getDeploymentId());
                });

            });

            //2.删除未部署的模型
            List<Model> notDeployedList = repositoryService.createModelQuery().modelId(modelId).notDeployed().list();
            notDeployedList.forEach(model -> {
                ModelEntity modelEntity = (ModelEntityImpl) model;
                modelRestMapper.deleteById(modelId);
                modelRestMapper.deleteByteArrayById(modelEntity.getEditorSourceValueId());
                modelRestMapper.deleteByteArrayById(modelEntity.getEditorSourceExtraValueId());
            });
        } catch (Exception e) {
            throw new SysWorkflowException("删除流程模型异常", e);
        }
    }

    @Override
    public Map<String, Object> getModelList(HttpServletRequest request) {
        ModelQuery modelQuery = repositoryService.createModelQuery();
        BpmnModelCmd bpmnModelCmd = BpmnBuilderUtil.buildBpmnCmd(request);
        String name = bpmnModelCmd.getName();
        String category = bpmnModelCmd.getCategory();
        String status = bpmnModelCmd.getStatus();
        int pageNum = bpmnModelCmd.getPageNum();
        int pageSize = bpmnModelCmd.getPageSize();
        if (StringUtils.isNoneBlank(name)) {
            modelQuery.modelNameLike(name);
        }
        if (StringUtils.isNoneBlank(category)) {
            modelQuery.modelCategory(category);
        }
        if (StringUtils.isNoneBlank(status) && Objects.equals(status, "1")) {
            modelQuery.deployed();
        } else if (StringUtils.isNoneBlank(status) && Objects.equals(status, "0")) {
            modelQuery.notDeployed();
        }
        long total = modelQuery.count();
        List<Model> models = modelQuery.orderByCreateTime().desc().listPage(pageNum - 1, pageSize);
        Map<String, Object> result = Maps.newHashMap();
        result.put("data", models);
        result.put("total", total);
        result.put("pageNum", pageNum);
        result.put("pageSize", pageSize);
        return result;
    }

    @Override
    public boolean deploymentModel(String modelId) {
        try {
            Model model = repositoryService.getModel(modelId);
            String modelName = model.getName();
            String modelCategory = model.getCategory();
            String defResourceName = model.getKey() + ".bpmn20.xml";
            String imgResourceName = model.getKey() + "." + model.getKey() + ".png";
            byte[] modelEditorSource = repositoryService.getModelEditorSource(modelId);

            InputStream inputStream = new ByteArrayInputStream(modelEditorSource);
            BpmnXMLConverter bpmnXMLConverter = new BpmnXMLConverter();
            XMLInputFactory factory = XMLInputFactory.newInstance();
            XMLStreamReader xmlStreamReader = factory.createXMLStreamReader(inputStream);
            BpmnModel bpmnModel = bpmnXMLConverter.convertToBpmnModel(xmlStreamReader);
            byte[] bpmnBytes = bpmnXMLConverter.convertToXML(bpmnModel);
            //根据 BpmnModel 生成图片输入流
//            InputStream flowImage = WorkflowUtils.getFlowImage(bpmnModel);

            Deployment deployment = repositoryService.createDeployment().name(modelName).category(modelCategory)
                    .addString(defResourceName, new String(bpmnBytes))
//                    .addBpmnModel(defResourceName, bpmnModel)
//                    .addInputStream(imgResourceName)
//                    .enableDuplicateFiltering()
                    .deploy();
            ProcessDefinition processDefinition = repositoryService.createProcessDefinitionQuery()
                    .deploymentId(deployment.getId()).singleResult();

            if (processDefinition == null) {
                return false;
            }
            //部署之后更新模型表中部署id字段
            model.setDeploymentId(deployment.getId());
            repositoryService.saveModel(model);
            repositoryService.setDeploymentCategory(deployment.getId(), modelCategory);
            repositoryService.setDeploymentKey(deployment.getId(), model.getKey());
            repositoryService.setProcessDefinitionCategory(processDefinition.getId(), modelCategory);
            logger.debug("部署模型{}成功", modelId);
            return true;
        } catch (Exception e) {
            throw new SysWorkflowException("模型[" + modelId + "]部署失败", e);
        }
    }

    @Override
    public boolean saveModel(HttpServletRequest request) {
        try {
            BpmnModelCmd bpmnModelCmd = BpmnBuilderUtil.buildBpmnCmd(request);
            String json_xml = bpmnModelCmd.getJsonXml();
            Model model = new ModelEntityImpl();
            ObjectNode modelNode = objectMapper.createObjectNode();
            modelNode.put("name", bpmnModelCmd.getName());
            modelNode.put("revision", model.getVersion());
            modelNode.put("description", bpmnModelCmd.getDescription());
            model.setMetaInfo(modelNode.toString());
            model.setKey(bpmnModelCmd.getKey());
            model.setName(bpmnModelCmd.getName());
            model.setCategory(bpmnModelCmd.getCategory());
            model.setVersion(1);

            repositoryService.saveModel(model);
            repositoryService.addModelEditorSource(model.getId(), json_xml.getBytes(StandardCharsets.UTF_8));

            InputStream svgStream = new ByteArrayInputStream(bpmnModelCmd.getSvgXml().getBytes(StandardCharsets.UTF_8));
            TranscoderInput input = new TranscoderInput(svgStream);
            PNGTranscoder transcoder = new PNGTranscoder();
            // Setup output
            ByteArrayOutputStream outStream = new ByteArrayOutputStream();
            TranscoderOutput output = new TranscoderOutput(outStream);
            // Do the transformation
            transcoder.transcode(input, output);
            final byte[] result = outStream.toByteArray();
            repositoryService.addModelEditorSourceExtra(model.getId(), result);
            outStream.close();
            return true;
        } catch (Exception e) {
            throw new SysWorkflowException("Error saving model", e);
        }
    }

    @Override
    public boolean updateModel(HttpServletRequest request, String modelId) {
        try (ByteArrayOutputStream outStream = new ByteArrayOutputStream()) {
            BpmnModelCmd bpmnModelCmd = BpmnBuilderUtil.buildBpmnCmd(request);
            String json_xml = bpmnModelCmd.getJsonXml();
            Model model = repositoryService.getModel(modelId);

            ObjectNode modelNode = objectMapper.createObjectNode();
            modelNode.put("name", bpmnModelCmd.getName());
            modelNode.put("revision", model.getVersion());
            modelNode.put("description", bpmnModelCmd.getDescription());
            model.setMetaInfo(modelNode.toString());
            model.setKey(bpmnModelCmd.getKey());
            model.setName(bpmnModelCmd.getName());
            model.setCategory(bpmnModelCmd.getCategory());
            model.setVersion(model.getVersion() + 1);

            repositoryService.saveModel(model);
            repositoryService.addModelEditorSource(model.getId(), json_xml.getBytes(StandardCharsets.UTF_8));
            InputStream svgStream = new ByteArrayInputStream(bpmnModelCmd.getSvgXml().getBytes(StandardCharsets.UTF_8));
            TranscoderInput input = new TranscoderInput(svgStream);
            PNGTranscoder transcoder = new PNGTranscoder();

            // Setup output
            TranscoderOutput output = new TranscoderOutput(outStream);
            // Do the transformation
            transcoder.transcode(input, output);
            final byte[] result = outStream.toByteArray();
            repositoryService.addModelEditorSourceExtra(model.getId(), result);
            return true;
        } catch (TranscoderException | IOException e) {
            throw new SysWorkflowException("修改流程模型异常", e);
        }
    }

    @Override
    public void getWorkflowImage(String modelId, HttpServletResponse response) {
        Model model = repositoryService.getModel(modelId);
        String deploymentId = model.getDeploymentId();
        ProcessDefinition processDefinition = repositoryService.createProcessDefinitionQuery()
                .deploymentId(deploymentId).singleResult();
        try (OutputStream os = response.getOutputStream();
             InputStream resourceAsStream = repositoryService.getProcessDiagram(processDefinition.getId())
        ) {
            byte[] b = new byte[1024];
            int len = -1;
            while (((len = resourceAsStream.read(b, 0, 1024)) != -1)) {
                response.getOutputStream().write(b, 0, len);
            }
            //设置输出文件的信息
            response.setCharacterEncoding("UTF-8");
            response.setContentType("image/png;charset=utf-8");
            response.setHeader("Pragma", "No-cache");
            response.setHeader("Cache-Control", "no-cache");
            response.setDateHeader("Expires", 0);
        } catch (IOException e) {
            throw new SysWorkflowException("获取流程模型图片异常", e);
        }
    }
}
